package controller

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"go-pan/internal/config"
	"go-pan/internal/log"
	"go-pan/internal/model"
	"go-pan/internal/repository"
	"go-pan/internal/service"
	"go-pan/internal/storage/provider"
	utils2 "go-pan/internal/utils"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

type FileHandler struct {
	LocalFileStorageProvider provider.StorageProvider
	FileMetaService          service.FileMetaService
	ShareService             service.ShareService
	FileChunkRepo            repository.FileChunkRepository
}

type FastUploadCheckRequest struct {
	Name       string `json:"name" binding:"required"`
	Size       int64  `json:"size" binding:"required"`
	MimeType   string `json:"mime_type"`
	HashSHA256 string `json:"hash_sha256" binding:"required,len=64"`
	ParentID   int64  `json:"parent_id"`
}

type FastUploadCheckResponse struct {
	Instant bool  `json:"instant"`
	FileID  int64 `json:"file_id,omitempty"`
}

func (h *FileHandler) ListFiles(c *gin.Context) {
	// 取当前用户
	userInfo, _ := utils2.GetUserInfo(c)

	// 可选：获取 parent_id（默认为 root = nil）
	parentIDStr := c.DefaultQuery("parent_id", "")
	var parentID int64
	if parentIDStr != "" {
		if id, err := strconv.ParseInt(parentIDStr, 10, 64); err == nil {
			parentID = id
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": "invalid parent_id"})
			return
		}
	}

	// 查询文件列表
	files, err := h.FileMetaService.ListFilesByParent(c.Request.Context(), userInfo.ID, parentID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list files"})
		return
	}

	// 响应
	c.JSON(http.StatusOK, gin.H{
		"files": files,
	})
}

type MkdirRequest struct {
	Name     string `json:"name" binding:"required"`
	ParentID int64  `json:"parent_id"`
}

// Mkdir creates a directory under parent_id (default root)
func (h *FileHandler) Mkdir(c *gin.Context) {
	var req MkdirRequest
	if err := c.ShouldBindJSON(&req); err != nil || req.Name == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}

	userInfo, ok := utils2.GetUserInfo(c)
	if !ok || userInfo == nil || userInfo.ID == 0 {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
		return
	}

	meta := &model.FileMetadata{
		OwnerID:       userInfo.ID,
		ParentID:      req.ParentID,
		Name:          req.Name,
		NameUniqueKey: fmt.Sprintf("dir/%d/%s", req.ParentID, req.Name),
		IsDir:         true,
		Size:          0,
		MimeType:      "",
		StorageKey:    "",
		StorageEngine: "local",
		IsDeleted:     false,
	}

	if err := h.FileMetaService.Create(c.Request.Context(), meta); err != nil {
		log.BizLogger.Error("create directory failed",
			zap.Int64("user_id", userInfo.ID),
			zap.String("dir_name", req.Name),
			zap.Int64("parent_id", req.ParentID),
			zap.Error(err))
		c.JSON(http.StatusBadRequest, gin.H{"error": "create dir failed: " + err.Error()})
		return
	}

	log.BizLogger.Info("directory created",
		zap.Int64("user_id", userInfo.ID),
		zap.Int64("dir_id", meta.ID),
		zap.String("dir_name", req.Name),
		zap.Int64("parent_id", req.ParentID))

	log.AuditLogger.Info("directory created",
		zap.Int64("user_id", userInfo.ID),
		zap.String("username", userInfo.Username),
		zap.Int64("dir_id", meta.ID),
		zap.String("dir_name", req.Name),
		zap.String("ip", c.ClientIP()),
		zap.Time("timestamp", time.Now()))

	c.JSON(http.StatusOK, gin.H{"id": meta.ID})
}

type RenameRequest struct {
	ID      int64  `json:"id" binding:"required"`
	NewName string `json:"new_name" binding:"required"`
}

func (h *FileHandler) Rename(c *gin.Context) {
	var req RenameRequest
	if err := c.ShouldBindJSON(&req); err != nil || req.ID <= 0 || req.NewName == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}

	userInfo, _ := utils2.GetUserInfo(c)
	meta, err := h.FileMetaService.GetByID(c.Request.Context(), req.ID)
	if err != nil || meta == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}
	if meta.OwnerID != userInfo.ID {
		c.JSON(http.StatusForbidden, gin.H{"error": "access denied"})
		return
	}

	// 检查新名称是否与目录下其他文件冲突
	newKey := fmt.Sprintf("%d/%d/%s", meta.OwnerID, meta.ParentID, req.NewName)
	existingFile, err := h.FileMetaService.FindByUniqueKey(c.Request.Context(), userInfo.ID, meta.ParentID, newKey)
	if err != nil {
		log.BizLogger.Error("check unique key failed",
			zap.Int64("user_id", userInfo.ID),
			zap.String("new_name", req.NewName),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "check failed"})
		return
	}
	// 如果找到了同名文件，且不是当前文件本身，则冲突
	if existingFile != nil && existingFile.ID != req.ID {
		c.JSON(http.StatusConflict, gin.H{"error": "file with same name already exists"})
		return
	}

	if err := h.FileMetaService.Rename(c.Request.Context(), req.ID, req.NewName, newKey); err != nil {
		log.BizLogger.Error("rename failed",
			zap.Int64("user_id", userInfo.ID),
			zap.Int64("file_id", req.ID),
			zap.String("old_name", meta.Name),
			zap.String("new_name", req.NewName),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "rename failed"})
		return
	}

	log.BizLogger.Info("file renamed",
		zap.Int64("user_id", userInfo.ID),
		zap.Int64("file_id", req.ID),
		zap.String("old_name", meta.Name),
		zap.String("new_name", req.NewName))

	log.AuditLogger.Info("file renamed",
		zap.Int64("user_id", userInfo.ID),
		zap.String("username", userInfo.Username),
		zap.Int64("file_id", req.ID),
		zap.String("old_name", meta.Name),
		zap.String("new_name", req.NewName),
		zap.String("ip", c.ClientIP()),
		zap.Time("timestamp", time.Now()))

	c.JSON(http.StatusOK, gin.H{"message": "ok"})
}

// FastUploadCheck: 秒传校验/去重
func (h *FileHandler) FastUploadCheck(c *gin.Context) {
	var req FastUploadCheckRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request: " + err.Error()})
		return
	}

	userInfo, ok := utils2.GetUserInfo(c)
	if !ok || userInfo == nil || userInfo.ID == 0 {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
		return
	}

	if req.ParentID == 0 {
		req.ParentID = 0
	}

	// 查询是否存在相同 hash+size 的文件（任意用户）
	exist, err := h.FileMetaService.FindByHashAndSize(c.Request.Context(), req.HashSHA256, req.Size)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "check failed"})
		return
	}

	if exist == nil {
		// 不存在 → 走正常上传
		c.JSON(http.StatusOK, FastUploadCheckResponse{Instant: false})
		return
	}

	// 存在 → 如果是不同用户，需要将对象复制到当前用户存储
	storageKeyToUse := exist.StorageKey
	storageEngine := exist.StorageEngine
	mimeType := chooseNonEmpty(req.MimeType, exist.MimeType)

	if exist.OwnerID != userInfo.ID {
		// 源/目的存储
		srcStorage, err := h.LocalFileStorageProvider.GetUserStorage(c, fmt.Sprintf("%d", exist.OwnerID))
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "source storage unavailable"})
			return
		}
		dstStorage, err := h.LocalFileStorageProvider.GetUserStorage(c, fmt.Sprintf("%d", userInfo.ID))
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "dest storage unavailable"})
			return
		}

		// 复制内容
		reader, err := srcStorage.Download(c, exist.StorageKey)
		if err != nil {
			c.JSON(http.StatusNotFound, gin.H{"error": "source file not found"})
			return
		}
		defer reader.Close()

		newKey := utils2.GenObjectKey()
		if err := dstStorage.Upload(c, newKey, reader, req.Size, mimeType); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "copy file failed"})
			return
		}
		storageKeyToUse = newKey
		storageEngine = exist.StorageEngine
	}

	// 在当前用户下新建元信息，指向可访问的对象
	nameKey := fmt.Sprintf("%d/%d/%s", userInfo.ID, req.ParentID, req.Name)
	meta := &model.FileMetadata{
		OwnerID:       userInfo.ID,
		ParentID:      req.ParentID,
		Name:          req.Name,
		NameUniqueKey: nameKey,
		IsDir:         false,
		Size:          exist.Size,
		MimeType:      mimeType,
		StorageKey:    storageKeyToUse,
		StorageEngine: storageEngine,
		ETag:          exist.ETag,
		HashSHA256:    exist.HashSHA256,
		IsDeleted:     false,
	}

	if err := h.FileMetaService.Create(c.Request.Context(), meta); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "create meta failed"})
		return
	}

	c.JSON(http.StatusOK, FastUploadCheckResponse{Instant: true, FileID: meta.ID})
}

func chooseNonEmpty(a, b string) string {
	if a != "" {
		return a
	}
	return b
}

type CreateShareRequest struct {
	FileID    int64  `json:"file_id" binding:"required"`
	Password  string `json:"password"`   // 可选密码，4位数
	ExpiredAt string `json:"expired_at"` // RFC3339，可空
}

type CreateShareResponse struct {
	Token string `json:"token"`
	Link  string `json:"link"`
}

// CreateShare 生成分享链接
func (h *FileHandler) CreateShare(c *gin.Context) {
	var req CreateShareRequest
	if err := c.ShouldBindJSON(&req); err != nil || req.FileID <= 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}
	userInfo, ok := utils2.GetUserInfo(c)
	if !ok || userInfo == nil || userInfo.ID == 0 {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
		return
	}
	meta, err := h.FileMetaService.GetByID(c.Request.Context(), req.FileID)
	if err != nil || meta == nil || meta.OwnerID != userInfo.ID {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}

	token, err := utils2.RandomToken(32)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "generate token failed"})
		return
	}

	var expiredAt time.Time
	if req.ExpiredAt != "" {
		t, parseErr := time.Parse(time.RFC3339, req.ExpiredAt)
		if parseErr != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "invalid expired_at"})
			return
		}
		expiredAt = t
	}

	// 处理密码（如果提供）：验证长度并加密
	var passwordHash string
	if req.Password != "" {
		if len(req.Password) != 4 {
			c.JSON(http.StatusBadRequest, gin.H{"error": "password must be 4 digits"})
			return
		}
		var hashErr error
		passwordHash, hashErr = utils2.HashPassword(req.Password)
		if hashErr != nil {
			log.BizLogger.Error("hash share password failed",
				zap.Int64("user_id", userInfo.ID),
				zap.Int64("file_id", req.FileID),
				zap.Error(hashErr))
			c.JSON(http.StatusInternalServerError, gin.H{"error": "hash password failed"})
			return
		}
	}

	share := &model.FileShare{
		FileID:    meta.ID,
		OwnerID:   userInfo.ID,
		Token:     token,
		Password:  passwordHash,
		ExpiredAt: expiredAt,
	}
	if err := h.ShareService.Create(c.Request.Context(), share); err != nil {
		log.BizLogger.Error("create share failed",
			zap.Int64("user_id", userInfo.ID),
			zap.Int64("file_id", req.FileID),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "create share failed"})
		return
	}

	link := fmt.Sprintf("/share.html?token=%s", token)

	log.BizLogger.Info("share link created",
		zap.Int64("user_id", userInfo.ID),
		zap.Int64("file_id", req.FileID),
		zap.String("token", token))

	log.AuditLogger.Info("share link created",
		zap.Int64("user_id", userInfo.ID),
		zap.String("username", userInfo.Username),
		zap.Int64("file_id", req.FileID),
		zap.String("file_name", meta.Name),
		zap.String("token", token),
		zap.String("ip", c.ClientIP()),
		zap.Time("expired_at", expiredAt),
		zap.Time("timestamp", time.Now()))

	c.JSON(http.StatusOK, CreateShareResponse{Token: token, Link: link})
}

// GetShareInfo 获取分享详情（公开）
func (h *FileHandler) GetShareInfo(c *gin.Context) {
	token := c.Param("token")
	if token == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid token"})
		return
	}
	s, err := h.ShareService.FindByToken(c.Request.Context(), token)
	if err != nil || s == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "share not found"})
		return
	}
	if !s.ExpiredAt.IsZero() && time.Now().After(s.ExpiredAt) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "share expired"})
		return
	}

	meta, err := h.FileMetaService.GetByID(c.Request.Context(), s.FileID)
	if err != nil || meta == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}

	// 获取用户名
	var owner model.User
	_ = config.DB.First(&owner, s.OwnerID).Error

	// 检查是否有密码保护
	hasPassword := s.Password != ""

	// 如果有密码，检查是否提供了 share_token
	if hasPassword {
		shareTokenJWT := c.GetHeader("X-Share-Token")
		if shareTokenJWT == "" {
			// 需要密码验证
			c.JSON(http.StatusOK, gin.H{
				"has_password":  true,
				"from_username": owner.Username,
				"file": gin.H{
					"id":        meta.ID,
					"name":      meta.Name,
					"size":      meta.Size,
					"mime_type": meta.MimeType,
					"is_dir":    meta.IsDir,
				},
			})
			return
		}

		// 验证 share_token
		shareToken, err := utils2.ValidateShareToken(shareTokenJWT)
		if err != nil || shareToken != token {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid share token"})
			return
		}
	}

	// 无密码或验证通过，返回完整信息
	c.JSON(http.StatusOK, gin.H{
		"has_password":  false,
		"from_username": owner.Username,
		"file": gin.H{
			"id":        meta.ID,
			"name":      meta.Name,
			"size":      meta.Size,
			"mime_type": meta.MimeType,
			"is_dir":    meta.IsDir,
		},
	})
}

type VerifySharePasswordRequest struct {
	Token    string `json:"token" binding:"required"`
	Password string `json:"password" binding:"required"`
}

type VerifySharePasswordResponse struct {
	ShareToken string `json:"share_token"`
}

// VerifySharePassword 验证分享密码并返回 share_token
func (h *FileHandler) VerifySharePassword(c *gin.Context) {
	var req VerifySharePasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil || req.Token == "" || req.Password == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}

	s, err := h.ShareService.FindByToken(c.Request.Context(), req.Token)
	if err != nil || s == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "share not found"})
		return
	}

	if !s.ExpiredAt.IsZero() && time.Now().After(s.ExpiredAt) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "share expired"})
		return
	}

	// 验证密码
	if !utils2.CheckPasswordHash(req.Password, s.Password) {
		log.AuthLogger.Warn("share password verification failed",
			zap.String("token", req.Token),
			zap.String("ip", c.ClientIP()))
		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid password"})
		return
	}

	// 生成 share_token
	shareToken, err := utils2.GenerateShareToken(req.Token)
	if err != nil {
		log.BizLogger.Error("generate share token failed",
			zap.String("share_token", req.Token),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "generate token failed"})
		return
	}

	log.BizLogger.Info("share password verified",
		zap.String("share_token", req.Token),
		zap.String("ip", c.ClientIP()))

	c.JSON(http.StatusOK, VerifySharePasswordResponse{ShareToken: shareToken})
}

// DownloadShare 下载分享的文件（公开）
func (h *FileHandler) DownloadShare(c *gin.Context) {
	token := c.Param("token")
	if token == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid token"})
		return
	}
	s, err := h.ShareService.FindByToken(c.Request.Context(), token)
	if err != nil || s == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "share not found"})
		return
	}
	if !s.ExpiredAt.IsZero() && time.Now().After(s.ExpiredAt) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "share expired"})
		return
	}

	// 如果有密码保护，验证 share_token
	if s.Password != "" {
		shareTokenJWT := c.GetHeader("X-Share-Token")
		if shareTokenJWT == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "share token required"})
			return
		}

		shareToken, err := utils2.ValidateShareToken(shareTokenJWT)
		if err != nil || shareToken != token {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid share token"})
			return
		}
	}

	meta, err := h.FileMetaService.GetByID(c.Request.Context(), s.FileID)
	if err != nil || meta == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}

	// 分享的文件已被删除，禁止下载
	if meta.IsDeleted {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}

	if meta.IsDir {
		c.JSON(http.StatusBadRequest, gin.H{"error": "cannot download directory"})
		return
	}

	// 从分享拥有者存储下载
	storage, err := h.LocalFileStorageProvider.GetUserStorage(c, fmt.Sprintf("%d", s.OwnerID))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "storage unavailable"})
		return
	}

	// 通过关联表判断：查询 file_chunk 表是否存在该文件的记录
	fileChunks, err := h.FileChunkRepo.GetByFileID(c.Request.Context(), meta.ID)
	if err == nil && len(fileChunks) > 0 {
		// 逻辑合并：从 file_chunk 表读取分片并顺序输出
		// 设置响应头
		c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", url.PathEscape(meta.Name)))
		c.Header("Content-Type", meta.MimeType)
		c.Header("Content-Length", fmt.Sprintf("%d", meta.Size))

		// 按顺序读取所有分片并输出
		for _, chunk := range fileChunks {
			chunkReader, err := storage.Download(c, chunk.StorageKey)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to read chunk %d", chunk.ChunkIndex)})
				return
			}
			if _, err := io.Copy(c.Writer, chunkReader); err != nil {
				chunkReader.Close()
				c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to write chunk %d", chunk.ChunkIndex)})
				return
			}
			chunkReader.Close()
		}
		return
	}

	// 普通文件：直接下载（storage_key 不为空）
	if meta.StorageKey == "" {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "file storage key not found"})
		return
	}
	reader, err := storage.Download(c, meta.StorageKey)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}
	defer reader.Close()

	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", url.PathEscape(meta.Name)))
	c.Header("Content-Type", meta.MimeType)
	c.Header("Content-Length", fmt.Sprintf("%d", meta.Size))
	io.Copy(c.Writer, reader)
}

func (h *FileHandler) Upload(c *gin.Context) {
	// 从表单取文件
	file, err := c.FormFile("file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "file not found"})
		return
	}
	parentIDStr := c.DefaultQuery("parent_id", "")
	var parentID int64
	if parentIDStr != "" {
		if id, err := strconv.ParseInt(parentIDStr, 10, 64); err == nil {
			parentID = id
		} else {
			parentID = 0
		}
	} else {
		parentID = 0
	}

	// 校验文件大小（示例：限制为 10MB）
	//if file.Size > 10<<20 { // 10MB
	//	c.JSON(http.StatusBadRequest, gin.H{"error": "file size exceeds limit"})
	//	return
	//}

	// 获取 Content-Type
	contentType := file.Header.Get("Content-Type")

	// 打开文件内容
	src, err := file.Open()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open file"})
		return
	}
	defer src.Close()

	// 获取用户信息并验证登录状态
	userInfo, _ := utils2.GetUserInfo(c)
	//if !ok || userInfo == nil || userInfo.ID == 0 {
	//	c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
	//	return
	//}
	//if userInfo.IsGuest {
	//	c.JSON(http.StatusUnauthorized, gin.H{"error": "guest user not allowed"})
	//	return
	//}

	// 生成 objectKey
	objectKey := utils2.GenObjectKey()

	// 文件引擎保存
	storage, err := h.LocalFileStorageProvider.GetUserStorage(c, fmt.Sprintf("%d", userInfo.ID))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get storage"})
		return
	}

	// 计算 SHA-256 同时上传
	hasher := sha256.New()
	tee := io.TeeReader(src, hasher)
	err = storage.Upload(c, objectKey, tee, file.Size, contentType)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "upload failed"})
		return
	}
	sum := hex.EncodeToString(hasher.Sum(nil))

	// 上传成功，保存文件元信息
	meta := &model.FileMetadata{
		OwnerID:       userInfo.ID,
		ParentID:      parentID, // 如果支持目录，可传参数
		Name:          file.Filename,
		NameUniqueKey: fmt.Sprintf("%s/%s", "root", file.Filename), // 你可以自己构造唯一键
		IsDir:         false,
		Size:          file.Size,
		MimeType:      contentType,
		StorageKey:    objectKey,
		HashSHA256:    sum,
		IsDeleted:     false,
	}

	err = h.FileMetaService.Create(c.Request.Context(), meta)
	if err != nil {
		// 如果保存失败，尝试删掉已上传的文件
		_ = storage.Delete(c, objectKey) // 忽略删除错误，仅记录日志更好
		c.JSON(http.StatusInternalServerError, gin.H{"error": "save file metadata failed"})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message":   "upload success",
		"objectKey": objectKey,
		"fileID":    meta.ID,
	})
}
func (h *FileHandler) Download(c *gin.Context) {
	// 获取 fileID（你也可以从 query 参数或 path 参数取）
	fileIDStr := c.Param("id")
	fileID, err := strconv.ParseInt(fileIDStr, 10, 64)
	if err != nil || fileID <= 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"})
		return
	}

	// 获取用户信息
	userInfo, _ := utils2.GetUserInfo(c)
	//if !ok || userInfo == nil || userInfo.ID == 0 {
	//	c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
	//	return
	//}
	//if userInfo.IsGuest {
	//	c.JSON(http.StatusUnauthorized, gin.H{"error": "guest user not allowed"})
	//	return
	//}

	// 获取元信息
	meta, err := h.FileMetaService.GetByID(c.Request.Context(), fileID)
	if err != nil || meta == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}

	// 权限校验：只能下载自己的文件
	if meta.OwnerID != userInfo.ID {
		c.JSON(http.StatusForbidden, gin.H{"error": "access denied"})
		return
	}

	// 不允许下载已删除的文件
	if meta.IsDeleted {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}

	// 目录不允许下载
	if meta.IsDir {
		c.JSON(http.StatusBadRequest, gin.H{"error": "cannot download directory"})
		return
	}

	// 获取存储对象
	storage, err := h.LocalFileStorageProvider.GetUserStorage(c, fmt.Sprintf("%d", userInfo.ID))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "storage unavailable"})
		return
	}

	// 通过关联表判断：查询 file_chunk 表是否存在该文件的记录
	fileChunks, err := h.FileChunkRepo.GetByFileID(c.Request.Context(), fileID)
	if err == nil && len(fileChunks) > 0 {
		// 逻辑合并：从 file_chunk 表读取分片并顺序输出
		// 设置响应头
		c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", url.PathEscape(meta.Name)))
		c.Header("Content-Type", meta.MimeType)
		c.Header("Content-Length", fmt.Sprintf("%d", meta.Size))

		// 按顺序读取所有分片并输出
		for _, chunk := range fileChunks {
			chunkReader, err := storage.Download(c, chunk.StorageKey)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to read chunk %d", chunk.ChunkIndex)})
				return
			}
			if _, err := io.Copy(c.Writer, chunkReader); err != nil {
				chunkReader.Close()
				c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to write chunk %d", chunk.ChunkIndex)})
				return
			}
			chunkReader.Close()
		}
		return
	}

	// 普通文件：直接下载（storage_key 不为空）
	if meta.StorageKey == "" {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "file storage key not found"})
		return
	}
	reader, err := storage.Download(c, meta.StorageKey)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}
	defer reader.Close()

	// 设置下载响应头
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", url.PathEscape(meta.Name)))
	c.Header("Content-Type", meta.MimeType)
	c.Header("Content-Length", fmt.Sprintf("%d", meta.Size))

	// 写入响应体
	io.Copy(c.Writer, reader)
}

func (h *FileHandler) Delete(c *gin.Context) {
	// 获取 fileID 参数
	fileIDStr := c.Param("id")
	fileID, err := strconv.ParseInt(fileIDStr, 10, 64)
	if err != nil || fileID <= 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"})
		return
	}

	// 获取当前登录用户信息
	userInfo, _ := utils2.GetUserInfo(c)
	//if !ok || userInfo == nil || userInfo.ID == 0 {
	//	c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
	//	return
	//}

	// 获取文件元信息
	meta, err := h.FileMetaService.GetByID(c.Request.Context(), fileID)
	if err != nil || meta == nil {
		log.BizLogger.Warn("delete file failed: file not found",
			zap.Int64("user_id", userInfo.ID),
			zap.Int64("file_id", fileID))
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}

	// 权限校验
	if meta.OwnerID != userInfo.ID {
		c.JSON(http.StatusForbidden, gin.H{"error": "access denied"})
		return
	}

	// 目录删除前校验是否为空（软删除不需要删除存储，只需要标记）
	if meta.IsDir {
		hasChildren, err := h.FileMetaService.HasChildren(c.Request.Context(), userInfo.ID, meta.ID)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "check children failed"})
			return
		}
		if hasChildren {
			c.JSON(http.StatusBadRequest, gin.H{"error": "directory not empty"})
			return
		}
	}
	// 注意：软删除不删除物理存储，只标记元信息为已删除

	// 标记元信息为 is_deleted = true
	err = h.FileMetaService.MarkDeleted(c.Request.Context(), fileID)
	if err != nil {
		log.BizLogger.Error("mark file as deleted failed",
			zap.Int64("user_id", userInfo.ID),
			zap.Int64("file_id", fileID),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "metadata update failed"})
		return
	}

	log.BizLogger.Info("file deleted",
		zap.Int64("user_id", userInfo.ID),
		zap.Int64("file_id", fileID),
		zap.String("file_name", meta.Name),
		zap.Bool("is_dir", meta.IsDir))

	log.AuditLogger.Info("file deleted",
		zap.Int64("user_id", userInfo.ID),
		zap.String("username", userInfo.Username),
		zap.Int64("file_id", fileID),
		zap.String("file_name", meta.Name),
		zap.Bool("is_dir", meta.IsDir),
		zap.String("ip", c.ClientIP()),
		zap.Time("timestamp", time.Now()))

	c.JSON(http.StatusOK, gin.H{"message": "delete success"})
}

// ListTrash 列出回收站的文件
func (h *FileHandler) ListTrash(c *gin.Context) {
	userInfo, _ := utils2.GetUserInfo(c)

	files, err := h.FileMetaService.ListDeletedFiles(c.Request.Context(), userInfo.ID)
	if err != nil {
		log.BizLogger.Error("list trash failed",
			zap.Int64("user_id", userInfo.ID),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "list trash failed"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"files": files})
}

// RestoreFile 恢复文件
func (h *FileHandler) RestoreFile(c *gin.Context) {
	fileIDStr := c.Param("id")
	fileID, err := strconv.ParseInt(fileIDStr, 10, 64)
	if err != nil || fileID <= 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"})
		return
	}

	userInfo, _ := utils2.GetUserInfo(c)
	meta, err := h.FileMetaService.GetByID(c.Request.Context(), fileID)
	if err != nil || meta == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}

	if meta.OwnerID != userInfo.ID {
		c.JSON(http.StatusForbidden, gin.H{"error": "access denied"})
		return
	}

	if !meta.IsDeleted {
		c.JSON(http.StatusBadRequest, gin.H{"error": "file is not deleted"})
		return
	}

	err = h.FileMetaService.Restore(c.Request.Context(), fileID)
	if err != nil {
		log.BizLogger.Error("restore file failed",
			zap.Int64("user_id", userInfo.ID),
			zap.Int64("file_id", fileID),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "restore failed"})
		return
	}

	log.BizLogger.Info("file restored",
		zap.Int64("user_id", userInfo.ID),
		zap.Int64("file_id", fileID),
		zap.String("file_name", meta.Name))

	log.AuditLogger.Info("file restored",
		zap.Int64("user_id", userInfo.ID),
		zap.String("username", userInfo.Username),
		zap.Int64("file_id", fileID),
		zap.String("file_name", meta.Name),
		zap.String("ip", c.ClientIP()),
		zap.Time("timestamp", time.Now()))

	c.JSON(http.StatusOK, gin.H{"message": "restore success"})
}

// HardDeleteFile 彻底删除文件
func (h *FileHandler) HardDeleteFile(c *gin.Context) {
	fileIDStr := c.Param("id")
	fileID, err := strconv.ParseInt(fileIDStr, 10, 64)
	if err != nil || fileID <= 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file id"})
		return
	}

	userInfo, _ := utils2.GetUserInfo(c)
	meta, err := h.FileMetaService.GetByID(c.Request.Context(), fileID)
	if err != nil || meta == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}

	if meta.OwnerID != userInfo.ID {
		c.JSON(http.StatusForbidden, gin.H{"error": "access denied"})
		return
	}

	if !meta.IsDeleted {
		c.JSON(http.StatusBadRequest, gin.H{"error": "file is not deleted"})
		return
	}

	// 如果是文件，删除物理存储
	if !meta.IsDir {
		storage, err := h.LocalFileStorageProvider.GetUserStorage(c, fmt.Sprintf("%d", userInfo.ID))
		if err != nil {
			log.BizLogger.Warn("storage unavailable for hard delete, proceeding with metadata deletion",
				zap.Int64("user_id", userInfo.ID),
				zap.Int64("file_id", fileID))
		} else if err := storage.Delete(context.Background(), meta.StorageKey); err != nil {
			log.BizLogger.Warn("physical file delete failed, proceeding with metadata deletion",
				zap.Int64("user_id", userInfo.ID),
				zap.Int64("file_id", fileID),
				zap.Error(err))
		}
	}

	// 物理删除元信息
	err = h.FileMetaService.HardDelete(c.Request.Context(), fileID)
	if err != nil {
		log.BizLogger.Error("hard delete file failed",
			zap.Int64("user_id", userInfo.ID),
			zap.Int64("file_id", fileID),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "hard delete failed"})
		return
	}

	log.BizLogger.Info("file hard deleted",
		zap.Int64("user_id", userInfo.ID),
		zap.Int64("file_id", fileID),
		zap.String("file_name", meta.Name))

	log.AuditLogger.Info("file hard deleted",
		zap.Int64("user_id", userInfo.ID),
		zap.String("username", userInfo.Username),
		zap.Int64("file_id", fileID),
		zap.String("file_name", meta.Name),
		zap.String("ip", c.ClientIP()),
		zap.Time("timestamp", time.Now()))

	c.JSON(http.StatusOK, gin.H{"message": "hard delete success"})
}

type MoveRequest struct {
	FileID      int64   `json:"file_id" binding:"required"`
	NewParentID int64   `json:"new_parent_id"`
	FileIDs     []int64 `json:"file_ids"` // 批量移动的文件ID列表
}

// MoveFile 移动单个文件/目录
func (h *FileHandler) MoveFile(c *gin.Context) {
	var req MoveRequest
	if err := c.ShouldBindJSON(&req); err != nil || req.FileID <= 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}

	userInfo, _ := utils2.GetUserInfo(c)
	meta, err := h.FileMetaService.GetByID(c.Request.Context(), req.FileID)
	if err != nil || meta == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
		return
	}

	if meta.OwnerID != userInfo.ID {
		c.JSON(http.StatusForbidden, gin.H{"error": "access denied"})
		return
	}

	// 检查目标位置是否存在同名文件
	newKey := fmt.Sprintf("%d/%d/%s", userInfo.ID, req.NewParentID, meta.Name)
	existingFile, err := h.FileMetaService.FindByUniqueKey(c.Request.Context(), userInfo.ID, req.NewParentID, newKey)
	if err != nil {
		log.BizLogger.Error("check unique key failed for move",
			zap.Int64("user_id", userInfo.ID),
			zap.Int64("file_id", req.FileID),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "check failed"})
		return
	}
	if existingFile != nil {
		c.JSON(http.StatusConflict, gin.H{"error": "file with same name already exists in destination"})
		return
	}

	// 执行移动
	if err := h.FileMetaService.Move(c.Request.Context(), req.FileID, req.NewParentID); err != nil {
		log.BizLogger.Error("move file failed",
			zap.Int64("user_id", userInfo.ID),
			zap.Int64("file_id", req.FileID),
			zap.Int64("old_parent_id", meta.ParentID),
			zap.Int64("new_parent_id", req.NewParentID),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "move failed"})
		return
	}

	log.BizLogger.Info("file moved",
		zap.Int64("user_id", userInfo.ID),
		zap.Int64("file_id", req.FileID),
		zap.String("file_name", meta.Name),
		zap.Int64("old_parent_id", meta.ParentID),
		zap.Int64("new_parent_id", req.NewParentID))

	log.AuditLogger.Info("file moved",
		zap.Int64("user_id", userInfo.ID),
		zap.String("username", userInfo.Username),
		zap.Int64("file_id", req.FileID),
		zap.String("file_name", meta.Name),
		zap.Int64("old_parent_id", meta.ParentID),
		zap.Int64("new_parent_id", req.NewParentID),
		zap.String("ip", c.ClientIP()),
		zap.Time("timestamp", time.Now()))

	c.JSON(http.StatusOK, gin.H{"message": "move success"})
}

// BatchMoveFiles 批量移动文件/目录
func (h *FileHandler) BatchMoveFiles(c *gin.Context) {
	var req MoveRequest
	if err := c.ShouldBindJSON(&req); err != nil || len(req.FileIDs) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}

	userInfo, _ := utils2.GetUserInfo(c)

	// 检查所有文件的权限和冲突
	for _, fileID := range req.FileIDs {
		meta, err := h.FileMetaService.GetByID(c.Request.Context(), fileID)
		if err != nil || meta == nil {
			c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("file %d not found", fileID)})
			return
		}

		if meta.OwnerID != userInfo.ID {
			c.JSON(http.StatusForbidden, gin.H{"error": "access denied"})
			return
		}

		// 检查目标位置是否存在同名文件
		newKey := fmt.Sprintf("%d/%d/%s", userInfo.ID, req.NewParentID, meta.Name)
		existingFile, err := h.FileMetaService.FindByUniqueKey(c.Request.Context(), userInfo.ID, req.NewParentID, newKey)
		if err != nil {
			log.BizLogger.Error("check unique key failed for batch move",
				zap.Int64("user_id", userInfo.ID),
				zap.Int64("file_id", fileID),
				zap.Error(err))
			c.JSON(http.StatusInternalServerError, gin.H{"error": "check failed"})
			return
		}
		if existingFile != nil {
			c.JSON(http.StatusConflict, gin.H{"error": fmt.Sprintf("file '%s' already exists in destination", meta.Name)})
			return
		}
	}

	// 批量执行移动（不使用事务，失败一条记录一条）
	successCount := 0
	failedCount := 0
	for _, fileID := range req.FileIDs {
		if err := h.FileMetaService.Move(c.Request.Context(), fileID, req.NewParentID); err != nil {
			failedCount++
			log.BizLogger.Error("batch move file failed",
				zap.Int64("user_id", userInfo.ID),
				zap.Int64("file_id", fileID),
				zap.Error(err))
		} else {
			successCount++
		}
	}

	log.BizLogger.Info("batch move files completed",
		zap.Int64("user_id", userInfo.ID),
		zap.Int("total", len(req.FileIDs)),
		zap.Int("success_count", successCount),
		zap.Int("failed_count", failedCount))

	log.AuditLogger.Info("batch move files",
		zap.Int64("user_id", userInfo.ID),
		zap.String("username", userInfo.Username),
		zap.Int("total", len(req.FileIDs)),
		zap.Int("success_count", successCount),
		zap.Int("failed_count", failedCount),
		zap.String("ip", c.ClientIP()),
		zap.Time("timestamp", time.Now()))

	c.JSON(http.StatusOK, gin.H{
		"message":       "batch move completed",
		"success_count": successCount,
		"failed_count":  failedCount,
	})
}
